#pragma once

#include <vex/core/unique_ptr_support.h>
#include <vex/iterable/contract/iterable.h>

namespace tests {
    namespace vc = vex::core;
    namespace vcc = vex::core::contract;
    namespace vi = vex::iterable;
    namespace vic = vex::iterable::contract;

    template<class TCategory = vex::iterable::trivial, class TRef = void>
    struct a_range_unique_ptr {
        typedef vc::a_unique_ptr< vic::range<TCategory, TRef> > type; 
    };

    class forward_unique_range_call {
        int m_client_called;
    public:
        forward_unique_range_call() : m_client_called(0) {}

        int call(vc::a_unique_ptr<vic::range<>>::type&& p_range) 
        {
            p_range->move_front();
            return ++m_client_called;
        }

        template< class TRef >
        int call(std::unique_ptr<vic::range<vex::iterable::output, TRef>, vc::unique_delete> && p_range) 
        {
            if (p_range->move_front()) {
                p_range->put(typename std::decay<TRef>::type());
            }
            ++m_client_called;
            return this->call(vc::a_unique_ptr<range<>>::type(std::move(p_range)));
        }

        template< class TRef >
        int call(std::unique_ptr<vic::range<vex::iterable::one_pass, TRef>, vc::unique_delete> && p_range) 
        {
            if (p_range->move_front()) {
                p_range->front();
            }
            ++m_client_called;
            
            return this->call(vc::a_unique_ptr<range<>>::type(std::move(p_range)));
        }

        template< class TRef >
        int call(std::unique_ptr<vic::range<vex::iterable::forward, TRef>, vc::unique_delete> && p_range) 
        {
            if (auto t_saved = p_range->save()) {
                t_saved->release();
            }

            ++m_client_called;
            
            return this->call(typename a_range_unique_ptr<vex::iterable::one_pass, TRef>::type(std::move(p_range)));
        }

        template< class TRef >
        int call(std::unique_ptr<vic::range<vex::iterable::bidirectional, TRef>, vc::unique_delete> && p_range)
        {
            if (auto t_saved = p_range->save()) {
                t_saved->release();
            }

            if (p_range->move_back()) {
                p_range->back();
            }

            ++m_client_called;
            
            return this->call(typename a_range_unique_ptr<vex::iterable::forward, TRef>::type(std::move(p_range)));
        }

        template< class TRef >
        int call(std::unique_ptr<vic::range<vex::iterable::random_access, TRef>, vc::unique_delete> && p_range) 
        {
            if (auto t_saved = p_range->save()) {
                t_saved->release();
            }

            if (p_range->size() > 0u) {
                p_range->at(0);
            }

            ++m_client_called;
            
            return this->call(typename a_range_unique_ptr<vex::iterable::bidirectional, TRef>::type(std::move(p_range)));
        }
    };
}